package com.itcoon.cloud.framework.webmvc.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.itcoon.cloud.framework.config.TemporalPatternConfig;
import com.itcoon.cloud.framework.properties.TemporalPatternConfigProperties;
import com.itcoon.cloud.framework.temporal.AbstractConverter;
import com.itcoon.cloud.framework.webmvc.config.properties.JacksonProperties;
import com.itcoon.cloud.framework.webmvc.registry.DefaultJacksonDeserializersRegistry;
import com.itcoon.cloud.framework.webmvc.registry.DefaultJacksonSerializersRegistry;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;

import java.io.IOException;
import java.io.Serializable;
import java.time.temporal.Temporal;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@EnableConfigurationProperties(JacksonProperties.class)
public class JacksonToMappingConfig {

    private final JacksonProperties jacksonProperties;

    public JacksonToMappingConfig(JacksonProperties jacksonProperties) {
        this.jacksonProperties = jacksonProperties;
    }



    @Bean
    @ConditionalOnMissingBean(Jackson2ObjectMapperBuilderCustomizer.class)
    public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer(TemporalPatternConfig temporalPatternConfig, List<JacksonConfigurer> configurers) {
        Map<Class<?>, JsonSerializer<?>> serializerMap = generateDefaultSerializerMap(temporalPatternConfig);
        Map<Class<?>, JsonDeserializer<?>> deserializerMap = generateDefaultDeserializerMap(temporalPatternConfig);

        // 自定义拓展序列化器覆盖默认序列化器
        DefaultJacksonSerializersRegistry serializersRegistry = new DefaultJacksonSerializersRegistry();
        DefaultJacksonDeserializersRegistry deserializersRegistry = new DefaultJacksonDeserializersRegistry();
        configurers.forEach(configurer->{configurer.registerSerializers(serializersRegistry); configurer.registerDeserializers(deserializersRegistry);});
        serializerMap.putAll(serializersRegistry.getSerializerMap());
        deserializerMap.putAll(deserializersRegistry.getDeserializerMap());

        return builder -> {
            builder
                    .serializersByType(serializerMap)
                    .deserializersByType(deserializerMap)
                    .featuresToDisable(
                            SerializationFeature.INDENT_OUTPUT,
                            SerializationFeature.WRITE_DATES_WITH_ZONE_ID,
                            SerializationFeature.WRITE_DATES_AS_TIMESTAMPS,
                            SerializationFeature.WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS
                    );
            if (!jacksonProperties.getIncludeNull()) {
                builder.serializationInclusion(JsonInclude.Include.NON_NULL);
            }
            builder.build()
                    .registerModule(new JavaTimeModule())
                    .setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY)
                    .enable(SerializationFeature.INDENT_OUTPUT)
                    .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
                    .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        };
    }


    private Map<Class<?>, JsonSerializer<?>> generateDefaultSerializerMap(TemporalPatternConfig temporalPatternConfig){
        Map<Class<?>, JsonSerializer<?>> serializerMap = new HashMap<>();
        for (TemporalPatternConfigProperties.TemporalSerialization<?, ?> temporalSerialization : temporalPatternConfig.getProperties().getTemporalSerializations()) {
            StdSerializer<?> serializer = getSerializer(temporalPatternConfig, (TemporalPatternConfigProperties.TemporalSerialization<Serializable, ?>) temporalSerialization);
            if(serializer != null){
                serializerMap.put(temporalSerialization.getTemporalType(),serializer);
            }
        }
        serializerMap.put(Long.class,new ToStringSerializer());
        serializerMap.put(Long.TYPE,new ToStringSerializer());
        return serializerMap;
    }

    private Map<Class<?>, JsonDeserializer<?>> generateDefaultDeserializerMap(TemporalPatternConfig temporalPatternConfig){
        Map<Class<?>, JsonDeserializer<?>> deserializerMap = new HashMap<>();
        for (TemporalPatternConfigProperties.TemporalSerialization<?, ?> temporalSerialization : temporalPatternConfig.getProperties().getTemporalSerializations()) {
            StdDeserializer<?> deserializer = getDeserializer(temporalPatternConfig, (TemporalPatternConfigProperties.TemporalSerialization<Serializable, ?>) temporalSerialization);
            if(deserializer != null){
                deserializerMap.put(temporalSerialization.getTemporalType(),  deserializer);
            }
        }
        return deserializerMap;
    }


    private <S extends Serializable, T extends Temporal> StdSerializer<T> getSerializer(TemporalPatternConfig temporalPatternConfig, TemporalPatternConfigProperties.TemporalSerialization<S, T>  temporalSerialization) {
        AbstractConverter<S, T> converter = temporalPatternConfig.getConverter(temporalSerialization.getSerialType(), temporalSerialization.getTemporalType());
        if(converter != null){
            return new StdSerializer<T>(temporalSerialization.getTemporalType()){
                @Override
                public void serialize(T t, JsonGenerator gen, SerializerProvider serializerProvider) throws IOException {
                    if(String.class.equals(temporalSerialization.getSerialType())){
                        gen.writeString((String)converter.format(t));
                    }
                    if(Long.class.equals(temporalSerialization.getSerialType())){
                        gen.writeNumber((Long)converter.format(t));
                    }
                }
            };
        }
        return null;
    }


    private <S extends Serializable, T extends Temporal> StdDeserializer<T> getDeserializer(TemporalPatternConfig temporalPatternConfig, TemporalPatternConfigProperties.TemporalSerialization<S, T>  temporalSerialization) {
        AbstractConverter<S, T> converter = temporalPatternConfig.getConverter(temporalSerialization.getSerialType(), temporalSerialization.getTemporalType());
        if(converter != null){
            return new StdDeserializer<T>(temporalSerialization.getTemporalType()){
                @Override
                public T deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException{
                    if(String.class.equals(temporalSerialization.getSerialType())){
                        return converter.parse((S)jsonParser.getValueAsString());
                    }
                    if(Long.class.equals(temporalSerialization.getSerialType())){
                        return converter.parse((S)Long.valueOf(jsonParser.getValueAsLong()));
                    }
                    return null;
                }
            };
        }
        return null;
    }
}
